
The goal of webmorphR is to make the construction of image stimuli more reproducible, with a focus on face stimuli.
This development of this package was funded by ERC grant #647910 (KINSHIP).
The sections below provide brief examples of what webmorphR can be used for. See articles for more detailed instructions on image manipulations, making figures, and making stimuli.
You can install the development version from GitHub with:
# install.packages("remotes")
remotes::install_github("debruine/webmorphR")
Installation can take a few minutes, depending on how many dependency packages you need to install.
library(webmorphR)
#>
#> ************
#> Welcome to webmorphR. For support and examples visit:
#> https://debruine.github.io/webmorphR/
#> ************
wm_opts(plot.maxwidth = 850) # set maximum width for plot output
In this example, we'll load a few faces from the CC-BY licensed Face Research Lab London Set, average them together, and create a figure.
Load 6 faces. If you haven't already downloaded the demo stimulus sets, this code will prompt you to download them. The demo sets are large, so this can take a minute or so.
# remotes::install_github("debruine/webmorphR.stim")
face_set <- demo_stim("london", "002|006|007|025|030|066")
plot(face_set, nrow = 1)
These faces already have webmorph templates, so you can make an average. The avg() function sends the images and templates to the server at webmorph.org, which does the processing and sends back the average, so it can take a few seconds. This also means you need an internet connection for this step.
Note: WebMorph was created because of the difficulty of installing desktop PsychoMorph on many computers, which is why this package uses a web-based API for averaging and transforming. You images are deleted from our server immediately after processing.
avg <- avg(face_set)
plot(avg)
Create a figure showing the individual faces and the average with the template superimposed. See plot_stim() for an explanation of the arguments to the plot() function (an alias for plot_stim).
# plot individual faces in a grid the same height as the average face
ind <- plot(face_set,
ncol = 2,
padding = 30,
external_pad = FALSE,
maxwidth = avg$avg$width,
maxheight = avg$avg$height)
# draw template on the face, join with individual grid, and plot
tem <- draw_tem(avg, pt.alpha = 0.5, line.alpha = 0.25)
# combine the ind and tem stimuli and plot
c(ind, tem) |> plot(nrow = 1)
In this example, we'll transform the individual images, mask and crop them, and put them together in a single compound figure.
First, transform images to make them more average. Transforming manipulates the shape, color, and/or texture of the trans_img by the specified proportion of the different between the from_img and the to_img. In this example, each individual face in face_set is transformed in shape only either by -50% of the difference between that that face an average face, making them more distinctive by exaggerating the non-average features, or by +50%, making them more average.
Set names for the shape, color or texture argument vector to automatically name the output stimuli. The trans() function also sends images to webmorph.org, so can take a minute and requires an internet connection.
dist_avg <- trans(trans_img = face_set,
from_img = face_set,
to_img = avg,
shape = c(distinctive = -0.5, average = 0.5),
color = 0, texture = 0)
plot(dist_avg, nrow = 2)
Next, mask the images with rainbow colours and crop them. Making them more distinctive has exaggerated differences in position on the image, so align the eyes to the average position first.
rainbow <- c("#983E82", "#E2A458", "#F5DC70",
"#59935B", "#467AAC", "#61589C")
stimuli <- dist_avg |>
align() |>
mask(c("face", "neck", "ears"), fill = rainbow) |>
crop(0.6, 0.8)
plot(stimuli, nrow = 2)
Save the stimuli into a new directory. This will create a folder in your working directory called “mystimuli” if it doesn't exist.
write_stim(stimuli, dir = "mystimuli")
Easily create figures to illustrate your research. The code below subsets the stimuli to reorder them with average faces first, edits the stimulus names to search and replace a part of the name, adds 120 pixels of black padding to the top of each image, labels them with their name, and plots all images in two rows.
c(subset(stimuli, "average"),
subset(stimuli, "distinctive")) |>
setnames(pattern = "_03_", replacement = " ") |>
pad(120, 0, 0, 0, fill = "black") |>
label(size = 90,
color = rainbow,
weight = 700,
gravity = "north",
location = "+0+10") |>
plot(nrow = 2)
Read in images with webmorph templates, or automatically delineate images with the python module face_recognition or the web-based software Face++. Auto-delineation with Face++ is more detailed, but requires a free API key from Face++. Auto-delineation with python doesn't transfer your images to a third party, but requires the webmorphR.dlib package and some python setup using reticulate.
Auto-delineation takes a few seconds per face, so you will see a progress bar in the console. You may also see some startup output from reticulate the first time you use this function in a session.
stimuli <- demo_stim("zoom") |>
resize(1/2) |>
auto_delin(replace = TRUE)
#> Error in py_run_file_impl(file, local, convert): Unable to open file '' (does it exist?)
draw_tem(stimuli, pt.size = 10) |> plot()
Alternatively, you can use the Face++ auto-delineator by setting model = "fpp106". This requires you to set up a Face++ account and set some environment variables (see the Making Stimuli vignette). It transfers your images to Face++, so make sure you read their privacy information.
stimuli <- demo_stim("zoom") |>
resize(1/2) |>
auto_delin(model = "fpp106", replace = TRUE)
draw_tem(stimuli, pt.size = 8) |> plot()
Now you can procrustes* align the images and crop them all to the same dimensions.
aligned <- stimuli |>
align(procrustes = TRUE, patch = TRUE) |>
crop_tem(120, 20, 20, 20)
plot(aligned)
Note: If you get an error message about rgl or dynlib when using align() with procrustes = TRUE, and are using a Mac, you may need to install XQuartz. You can omit the procrustes argument to default to 2-point alignment, which rotates and resizes all images so the pupils are in the same position (the average of the set, unless you manually specify positions).
Add 50 pixels of image labels.
labelled <- aligned |>
pad(50, 0, 0, 0, fill = "black") |>
label(c("15cm", "30cm", "45cm", "60cm"),
color = "white",
size = 40,
weight = 700,
location = "+0+5")
plot(labelled)
Turn your images into an animated gif. Make sure you have the gifski package installed (it doesn't need to be loaded) to take advantage of faster gif-making algorithms.
animate(labelled, fps = 2)